// Copyright DustedPixels.com 2008. All rights reserved.

package com.dustedpixels.asm;

import java.util.HashMap;
import java.util.Map;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.LocalVariablesSorter;

import com.dustedpixels.asm.Types;

/**
 * Inlines code of visited method into given {@literal LocalVariableVisitor},
 * which is assumed to be positioned in the place of injection.
 * 
 * @author micapolos@gmail.com (Michal Pociecha-Los)
 */
public final class CodeInliner extends MethodAdapter {
  private final LocalVariablesSorter out;
  private final Map<Integer, Integer> localsMap;
  private final Label gotoLabel;
  private final boolean isStatic;
  private final String desc;

  public CodeInliner(LocalVariablesSorter out, boolean isStatic, String desc) {
    super(out);
    this.out = out;
    this.localsMap = new HashMap<Integer, Integer>();
    this.gotoLabel = new Label();
    this.isStatic = isStatic;
    this.desc = desc;
  }

  @Override
  public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
    // Ignore everything except code.
    return null;
  }

  @Override
  public AnnotationVisitor visitAnnotationDefault() {
    // Ignore everything except code.
    return null;
  }

  @Override
  public void visitAttribute(Attribute attr) {
    // Ignore everything except code.
  }

  @Override
  public void visitCode() {
    Type[] argTypes = Type.getArgumentTypes(desc);
    
    int staticShift = isStatic ? 0 : 1;
    for (int argIndex = argTypes.length - 1; argIndex >= 0; argIndex--) {
      Type argType = argTypes[argIndex];
      int var = getLocal(argIndex + staticShift, argType);
      super.visitVarInsn(OpcodeUtils.getStoreOpcode(argType), var);
    }
    
    // For non-static method, pop target object reference from the stack
    if (!isStatic) {
      int local = getLocal(0, Types.ofObject());
      super.visitVarInsn(OpcodeUtils.getStoreOpcode(Types.ofObject()), local);
    }
  }

  @Override
  public void visitIincInsn(int var, int increment) {
    super.visitIincInsn(getLocal(var, Type.INT_TYPE), increment);
  }

  @Override
  public void visitLocalVariable(String name, String desc, String signature, Label start,
      Label end, int index) {
    // Ignore everything except code.
  }

  @Override
  public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
    // Ignore everything except code.
    return null;
  }

  @Override
  public void visitInsn(int opcode) {
    /*
     * In case we encounter RETURN instruction, replace it with GOTO instruction
     * which will jump at the end of the method. Otherwise, simply output
     * original instruction.
     */
    switch (opcode) {
      case Opcodes.IRETURN:
      case Opcodes.LRETURN:
      case Opcodes.FRETURN:
      case Opcodes.DRETURN:
      case Opcodes.RETURN:
        super.visitJumpInsn(Opcodes.GOTO, gotoLabel);
      default:
        super.visitInsn(opcode);
    }
  }

  @Override
  public void visitVarInsn(int opcode, int var) {
    Type type;
    
    switch (opcode) {
      case Opcodes.ILOAD:
      case Opcodes.ISTORE:
        type = Type.INT_TYPE;
        break;
      case Opcodes.LLOAD:
      case Opcodes.LSTORE:
        type = Type.INT_TYPE;
        break;
      case Opcodes.FLOAD:
      case Opcodes.FSTORE:
        type = Type.INT_TYPE;
        break;
      case Opcodes.DLOAD:
      case Opcodes.DSTORE:
        type = Type.INT_TYPE;
        break;
      case Opcodes.ALOAD:
      case Opcodes.ASTORE:
        type = Type.INT_TYPE;
        break;
      case Opcodes.RET:
        type = Type.VOID_TYPE;
        break;
      default:
        throw new IllegalArgumentException("Illegal opcode");
    }
    
    if (type != null) {
      super.visitVarInsn(opcode, getLocal(var, type));
    }
  }
  
  @Override
  public void visitLineNumber(int line, Label start) {
    // Ignore everything except code.
  }

  @Override
  public void visitMaxs(int maxStack, int maxLocals) {
    // Ignore everything except code.
  }

  @Override
  public void visitEnd() {
    /*
     * At the end of the method visit a label where GOTO instructions will jump to.
     */
    super.visitLabel(gotoLabel);
  }
  
  private int getLocal(int var, Type  type) {
    if (!localsMap.containsKey(var)) {
      var = localsMap.put(var, out.newLocal(type));
    }
    return var;
  }
}
